package com.alicp.jetcache.embedded;

import com.alicp.jetcache.CacheValueHolder;
import com.google.common.cache.CacheBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

/**
 * @author : wei.zhang
 * @Project: jetcache
 * @Package com.alicp.jetcache.embedded
 * @Description: TODO
 * @date Date : 2022年01月10日 14:30
 */

public class GuavaCache <K, V> extends AbstractEmbeddedCache<K, V>{

    private static Logger logger = LoggerFactory.getLogger(GuavaCache.class);


    private com.google.common.cache.Cache cache;

    public GuavaCache(EmbeddedCacheConfig<K, V> config) {
        super(config);
        addToCleaner();
    }

    private void addToCleaner() {
        Cleaner.addGuavaCache(this);
    }

    public void cleanExpiredEntry(){
        Set<Map.Entry<K, V>>  set = cache.asMap().entrySet();
        for (Map.Entry<K, V> x:set){
            V value = x.getValue();
            if (value != null && value instanceof CacheValueHolder) {
                CacheValueHolder h = (CacheValueHolder) value;
                if (System.currentTimeMillis() >= h.getExpireTime()) {
                    cache.invalidate(x.getKey());
                }
            } else {
                // assert false
                if (value == null) {
                    logger.error("key " + x.getKey() + " is null");
                } else {
                    logger.error("value of key " + x.getKey() + " is not a CacheValueHolder. type=" + value.getClass());
                }
            }
        }
    }

    @Override
    protected InnerMap createAreaCache() {
        final boolean isExpireAfterAccess = config.isExpireAfterAccess();
        final long expireAfterAccess = config.getExpireAfterAccessInMillis();
        CacheBuilder builder = CacheBuilder.newBuilder()
                .maximumSize(config.getLimit());
        if (isExpireAfterAccess){
            builder.expireAfterAccess(config.getExpireAfterAccessInMillis(), TimeUnit.MILLISECONDS);
        }
        cache = builder.build();
        return new InnerMap(){

            @Override
            public Object getValue(Object key) {
                return cache.getIfPresent(key);
            }

            @Override
            public Map getAllValues(Collection keys) {
                return cache.getAllPresent(keys);
            }

            @Override
            public void putValue(Object key, Object value) {
                cache.put(key,value);
            }

            @Override
            public void putAllValues(Map map) {
                cache.putAll(map);
            }

            @Override
            public boolean removeValue(Object key) {
                return cache.asMap().remove(key) != null;
            }

            @Override
            public boolean putIfAbsentValue(Object key, Object value) {
                return cache.asMap().putIfAbsent(key, value) == null;
            }

            @Override
            public void removeAllValues(Collection keys) {
                cache.invalidateAll(keys);
            }
        };
    }

    @Override
    public <T> T unwrap(Class<T> clazz) {
//        CacheBuilder.newBuilder().build()
        if (clazz.equals(com.google.common.cache.Cache.class)) {
            return (T) cache;
        }
        throw new IllegalArgumentException(clazz.getName());
    }
}
